// SPDX-License-Identifier: GPL-2.0-only

#include <linux/fs.h>
#include <linux/net.h>
#include <linux/string.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/ctype.h>
#include "cifs_quick_mode.h"

static struct quick_mode_para g_quick_para[MAX_QUICK_PARA_NUMS];

static void add_ffwd_accelerate_proc(bool proc, struct quick_mode_para *para)
{
	uint32_t ret = 0;
	struct tr143_pkt_ctrl samba_ctrl = {0};
	func_ffwd_tuple_accelerate accelerate = (func_ffwd_tuple_accelerate)
		__symbol_get("ffwd_do_local_tuple_accelerate");
	if (accelerate == NULL) {
		pr_err("Symbol not found\n");
		return;
	}

	samba_ctrl.owner = 2;
	samba_ctrl.proto = IPPROTO_TCP;
	samba_ctrl.speed_dir = SPEED_DIR_BOTH;
	samba_ctrl.sip = para->srcIp;
	samba_ctrl.dip = para->destIp;
	samba_ctrl.sport = para->srcPort;
	samba_ctrl.dport = para->destPort;
	samba_ctrl.stack = 0;
	ret = accelerate(&samba_ctrl, proc);
	if (ret != 0)
		pr_err("Failed to process the ffwd request. ErrorCode:[%u] Proc:[%d]\n",
			ret, proc);
}

struct quick_mode_para *get_para_by_id(uint32_t id)
{
	uint32_t i = 0;

	for (i = 0; i < MAX_QUICK_PARA_NUMS; i++) {
		if (g_quick_para[i].id == id)
			return &g_quick_para[i];
	}

	return NULL;
}

struct quick_mode_para *gen_qucik_mode_para(uint32_t id, struct sockaddr_in *peer,
	struct sockaddr_in *local)
{
	int32_t index = -1;
	uint32_t i = 0;

	for (i = 0; i < MAX_QUICK_PARA_NUMS; i++) {
		if (g_quick_para[i].srcIp == peer->sin_addr.s_addr)
			return NULL;

		if (g_quick_para[i].id == 0)
			index = i;
	}

	if (index == -1 || index >= MAX_QUICK_PARA_NUMS)
		return NULL;

	g_quick_para[index].id = id;
	g_quick_para[index].srcIp = (unsigned int)local->sin_addr.s_addr;
	g_quick_para[index].destIp = (unsigned int)peer->sin_addr.s_addr;
	g_quick_para[index].srcPort = local->sin_port;
	g_quick_para[index].destPort = peer->sin_port;

	return &g_quick_para[index];
}

void clean_all_quick_para(void)
{
	uint32_t i = 0;

	for (i = 0; i < MAX_QUICK_PARA_NUMS; i++) {
		if (g_quick_para[i].id != 0)
			add_ffwd_accelerate_proc(0, &g_quick_para[i]);
	}
}

static void add_ffwd_accelerate_start(uint32_t id, struct sockaddr_in *peer,
	struct sockaddr_in *local)
{
	struct quick_mode_para *para = NULL;

	if (local->sin_family != AF_INET)
		return;

	para = get_para_by_id(id);
	if (para != NULL) {
		add_ffwd_accelerate_proc(0, para);
		memset(para, 0, sizeof(struct quick_mode_para));
	}

	para = gen_qucik_mode_para(id, peer, local);
	if (para == NULL)
		return;

	add_ffwd_accelerate_proc(1, para);
}

void add_ffwd_accelerate_stop(uint32_t id)
{
	struct quick_mode_para *para = get_para_by_id(id);

	if (para == NULL)
		return;

	add_ffwd_accelerate_proc(0, para);
	memset(para, 0, sizeof(struct quick_mode_para));
}

void check_for_add_ffwd_accelerate_start(struct TCP_Server_Info *server)
{
	struct sockaddr_in peer_addr = {0};
	struct sockaddr_in local_addr = {0};

	if (kernel_getpeername(server->ssocket, (struct sockaddr *)&peer_addr) < 0)
		pr_err("getpeername failed\n");

	if (kernel_getsockname(server->ssocket, (struct sockaddr *)&local_addr) < 0)
		pr_err("getsockname failed\n");

	if (server->quick_mode != 0)
		add_ffwd_accelerate_start((uint32_t)(uintptr_t)server, &peer_addr, &local_addr);
}
